/*--------------------------------------------------------------------------
 *  Copyright 2009 Taro L. Saito
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *--------------------------------------------------------------------------*/
//--------------------------------------
// XerialJ
//
// SilkNode.java
// Since: Jan 30, 2009 6:58:59 PM
//
// $URL$
// $Author$
//--------------------------------------
package org.xerial.silk.model;

import java.util.ArrayList;

import org.xerial.silk.parser.token.SilkTextToken;
import org.xerial.util.StringUtil;

/**
 * SilkNode begins with '-' character, followed by object notation of the form:
 * 
 * <pre>
 *  object_name (: nodeValue)? '(' attribute, ... ')' ([format_name])? ('*' | '&gt;' | '|')
 * </pre>
 * 
 * @author leo
 * 
 */
public class SilkNode implements SilkElement
{

    public final int                 indent;
    public final CharSequence        name;
    public final SilkValue           value;

    public final CharSequence        dataType;
    public final SilkNodeType        nodeType;
    public final SilkNodeModifier    modifier;
    public final ArrayList<SilkNode> childNodeList;

    protected SilkNode(int indent, CharSequence name, SilkValue value, CharSequence dataType, SilkNodeType nodeType,
            SilkNodeModifier modifier, ArrayList<SilkNode> childNodeList) {

        this.indent = indent;
        this.name = name;
        this.value = value;
        this.dataType = dataType;
        this.nodeType = nodeType;
        this.modifier = modifier;
        this.childNodeList = childNodeList;
    }

    public CharSequence getName() {
        return name;
    }

    public boolean hasChildren() {
        return !childNodeList.isEmpty();
    }

    // indentation level -1 means relative to the parent node
    public final static int RELATIVE = -1;

    /**
     * Return the indent level (the length of the leading white spaces) of this
     * node.
     * 
     * @return the indent level of the node, or {@link SilkNode#RELATIVE} if no
     *         indent is specified.
     */
    public int getIndentLevel() {
        return indent;
    }

    public boolean hasDataType() {
        return dataType != null;
    }

    public boolean hasValue() {
        return value != null;
    }

    static String getIndentString(int indent) {
        StringBuilder s = new StringBuilder(Math.max(indent, 0));
        for (int i = 0; i < indent; ++i) {
            s.append(' ');
        }
        return s.toString();
    }

    @Override
    public String toString() {
        StringBuilder s = new StringBuilder();
        s.append(getIndentString(indent));
        if (nodeType != SilkNodeType.SINGLE || (nodeType == SilkNodeType.SINGLE && indent != RELATIVE)) {
            s.append(nodeType);
        }
        if (name != null)
            s.append(name);
        if (hasDataType()) {
            s.append("<");
            s.append(dataType);
            s.append(">");
        }
        if (childNodeList != null && !childNodeList.isEmpty()) {
            s.append("(");
            s.append(StringUtil.join(childNodeList, ", "));
            s.append(")");
        }
        if (modifier != SilkNodeModifier.DEFAULT) {
            s.append(modifier.symbol);
        }
        if (value != null) {
            if (name != null)
                s.append(":");
            s.append(SilkTextToken.toVisibleString(value.getValue()));
        }
        return s.toString();
    }

    public String toSilk() {
        StringBuffer buf = new StringBuffer();
        if (indent != RELATIVE) {
            for (int i = 0; i < indent; ++i)
                buf.append(" ");
        }

        buf.append(toSilkAttribute());

        return buf.toString();
    }

    private String toSilkAttribute() {
        StringBuffer buf = new StringBuffer();
        if (name != null)
            buf.append(name);

        if (dataType != null) {
            buf.append("[");
            buf.append(dataType);
            buf.append("]");
        }

        if (value != null) {
            buf.append(":");
            buf.append(value.getValue());
        }

        if (!childNodeList.isEmpty()) {
            buf.append("(");
            ArrayList<String> childNodes = new ArrayList<String>();
            for (SilkNode child : childNodeList)
                childNodes.add(child.toSilkAttribute());
            buf.append(StringUtil.join(childNodes, ", "));
            buf.append(")");
        }

        buf.append(nodeType);

        return buf.toString();
    }

    /**
     * SilkNode builder
     * 
     * @author leo
     * 
     */
    public static class SilkNodeBuilder
    {
        private int                 indent   = RELATIVE;
        private CharSequence        name;
        private SilkValue           value;

        private CharSequence        dataType;
        private SilkNodeType        nodeType = SilkNodeType.SINGLE;
        private SilkNodeModifier    modifier = SilkNodeModifier.DEFAULT;
        private ArrayList<SilkNode> childNodeList;

        public SilkNode build() {
            return new SilkNode(indent, name, value, dataType, nodeType, modifier, childNodeList);
        }

        public SilkPreamble buildPreamble() {
            return new SilkPreamble(indent, name, value, dataType, SilkNodeType.PREAMBLE, childNodeList);
        }

        public SilkFunction buildFunction() {
            return new SilkFunction(indent, name, value, dataType, SilkNodeType.FUNCTION, childNodeList);
        }

        public SilkNodeBuilder setModifier(SilkNodeModifier modifier) {
            this.modifier = modifier;
            return this;
        }

        public SilkNodeBuilder setName(CharSequence name) {
            this.name = name;
            return this;
        }

        public SilkNodeBuilder setIndent(int indentSize) {
            indent = indentSize;
            return this;
        }

        public SilkNodeBuilder setDataType(CharSequence dataType) {
            this.dataType = dataType;
            return this;
        }

        public SilkNodeBuilder addSilkNode(SilkNodeBuilder newNode) {
            if (childNodeList == null)
                childNodeList = new ArrayList<SilkNode>();
            childNodeList.add(newNode.build());
            return this;
        }

        public SilkNodeBuilder setValue(CharSequence text) {
            this.value = new SilkTextValue(text);
            return this;
        }

        public SilkNodeBuilder setNodeType(SilkNodeType nodeType) {
            this.nodeType = nodeType;
            return this;
        }

    }

    @Override
    public Type getType() {
        return Type.Node;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <E extends SilkElement> E cast() {
        return (E) this;
    }

}
